package com.chhuang.core.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * sql查询拦截器
 * <p>
 * postgresql数据库模糊查询时忽略大小写 like 替换为 ilike
 * </p>
 * @author lsh
 * @version 1.0
 * @date 2022/5/10 11:13
 */
@Slf4j
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class FuzzyQueryInterceptor implements Interceptor{
    private static final String POSTGRE_DB_TABLE = "postgreSQL";
    private static final String LIKE_REGEX = " (?i)like ";
    private static final String IGNORE_LIKE_KEY = " ILIKE ";
    private static final String ORDER_BY_KEY = " order by ";
    private static final String DESC_REGEX = "\\b(?i)desc\\b";
    private static final String ASC_REGEX = "\\b(?i)asc\\b";
    private static final String DESC_NULLS_LAST_KEY = "DESC NULLS LAST";
    private static final String ASC_NULLS_LAST_KEY = "ASC NULLS LAST";

    private static final Pattern POSTGRE_DB_TABLE_PATTERN = Pattern.compile(POSTGRE_DB_TABLE, Pattern.CASE_INSENSITIVE);
    private static final Pattern LIKE_PATTERN = Pattern.compile(LIKE_REGEX, Pattern.CASE_INSENSITIVE);
    private static final Pattern ORDER_PATTERN = Pattern.compile(ORDER_BY_KEY, Pattern.CASE_INSENSITIVE);
    private static final Pattern DESC_PATTERN = Pattern.compile(DESC_REGEX, Pattern.CASE_INSENSITIVE);
    private static final Pattern ASC_PATTERN = Pattern.compile(ASC_REGEX, Pattern.CASE_INSENSITIVE);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //拦截StatementHandler，获取Mybatis对象属性
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = MetaObject
                .forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                        new DefaultReflectorFactory());
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        //执行mapper方法的全路径名
        String id = mappedStatement.getId();
        //只拦截查询语句，将like关键字替换成ilike，以忽略大小写
        if (SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())){
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();
            if (POSTGRE_DB_TABLE_PATTERN.matcher(sql).find()) {
                String mSql = sql;
                if (LIKE_PATTERN.matcher(mSql).find()) {
                    mSql = mSql.replaceAll(LIKE_REGEX, IGNORE_LIKE_KEY);
                }

                Matcher matcher = ORDER_PATTERN.matcher(mSql);
                if (matcher.find()) {
                    int orderByEnd = matcher.end();

                    String orderStr = mSql.substring(orderByEnd);

                    if (DESC_PATTERN.matcher(orderStr).find()) {
                        orderStr = orderStr.replaceAll(DESC_REGEX, DESC_NULLS_LAST_KEY);
                    }

                    if (ASC_PATTERN.matcher(orderStr).find()) {
                        orderStr = orderStr.replaceAll(ASC_REGEX, ASC_NULLS_LAST_KEY);
                    }

                    mSql = mSql.substring(0, orderByEnd) + orderStr;
                }

                if (!sql.equals(mSql)) {
                    log.debug(id + "：原始查询语句 ==> " + sql);
                    log.debug(id + "：替换后的查询语句 ==> " + mSql);
                    //通过反射修改sql语句
                    Field field = boundSql.getClass().getDeclaredField("sql");
                    field.setAccessible(true);
                    field.set(boundSql, mSql);
                }
            }
        }
        //执行结果
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

